package com.ft.javaio.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

/**
 * @Author RenPu
 * @Date 2023/7/19 10:38
 * @Version 1.0
 * @Description: 基于NIO多路复用技术彻底重构服务端程序
 **/
public class NIOServer {

    private static CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    private static CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

    //NIO,核心组件，bytebuf,selector,Chanel

   private static ByteBuffer readBuffer;
    private static Selector selector;


    public static void main(String[] args) {

        //初始化服务端方法
        init();

        //监听处理事件
        listern();

    }


    /**
     * 初始化NIO服务端，buf,serverChanel,selector组件
     *  1：绑定服务暴露的端口
     *  2：设置为非阻塞
     *  3：注册复用组件以及声明监测事件
     */
    public static  void init() {
      //申请数据存储空间
        readBuffer=ByteBuffer.allocate(1024*1024);
     try{
       //开启serverSocketChannel通道
       ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
       //设置为非阻塞，默认阻塞IO
       serverSocketChannel.configureBlocking(false);
       //绑定服务暴露的端口 在 bind() 方法中，通过指定第二个参数来设置最大排队连接数。在你的示例代码中，
       // 第二个参数设置为 100，表示服务器套接字的等待队列最大可以容纳 100 个连接请求。
       serverSocketChannel.socket().bind(new InetSocketAddress(9000),100);
       //开启selector复用组件
       selector = Selector.open();
       //selector复用组件注册到serverSocketChannel通道，并声明监测的事件类型
       serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
   }catch (Exception e){
       e.printStackTrace();
   }

}


    /**
     * 处理selector监测的事件
     * 1：开启复用组件监测机制
     * 2：迭代遍历，监听到事件集合，交由handlerKey,进行处理，同时移除集合的key
     */
  private static void listern() {
        while(true) {
            try {
                //开启复用组件监测机制
                selector.select();
                //迭代遍历，监听到事件集合，交由handlerKey,进行处理，同时移除集合的key
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    handlerKey(key);
                }
            }catch (Throwable e){
                e.printStackTrace();
            }

        }
    }


    /**
     * 根据SelectionKey状态，做不通的处理
     * 是否可建立连接的：isAcceptable （如果说这个key是个acceptable，是个连接请求的话）
     * 是否可读的：isReadable
     * @param key
     */
    private static void handlerKey(SelectionKey key) {
        SocketChannel channel = null;
        try{
            if(key.isAcceptable()){
                ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                channel = serverChannel.accept();  // 调用这个accept方法，就可以跟客户端进行TCP三次握手
                // 如果三次握手成功了之后，就可以获取到一个建立好TCP连接的SocketChannel
                // 这个SocketChannel大概可以理解为，底层有一个Socket，是跟客户端进行连接的
                // 你的SocketChannel就是联通到那个Socket上去，负责进行网络数据的读写的
                channel.configureBlocking(false);
                channel.register(selector, SelectionKey.OP_READ);// 仅仅关注这个READ请求，就是人家发送数据过来的请求
            }  // 如果说这个key是readable，是个发送了数据过来的话，此时需要读取客户端发送过来的数据
            else if(key.isReadable()){
                channel = (SocketChannel) key.channel();
                readBuffer.clear(); // buffer的用法，position = 0，limit = capacity，复位
                int count = channel.read(readBuffer); // 通过底层的socket读取数据，写入buffer中，position可能就会变成21之类的
                // 你读取到了多少个字节，此时buffer的position就会变成多少

                if(count > 0){
                    readBuffer.flip();
                    CharBuffer charBuffer = decoder.decode(readBuffer);
                    String request = charBuffer.toString();
                    System.out.println(request);
                    String response = "";
                    channel.write(encoder.encode(CharBuffer.wrap(response)));
                }
                else{
                    channel.close();
                }
            }
        }
        catch(Throwable t){
            t.printStackTrace();
            if(channel != null){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }




}
